/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.virtual.model.sax;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Properties;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.Workspace;
import javax.jcr.nodetype.NodeType;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import offset.nodes.Constants;
import offset.nodes.client.editor.model.PrintHandler;
import offset.nodes.client.virtual.model.jcr.SimpleNode;
import offset.nodes.server.html.model.NodeSubstituter;
import org.apache.jackrabbit.core.TransientRepository;
import org.apache.jackrabbit.util.ISO9075;
import org.xml.sax.SAXException;

/**
 * Implements a ContentSource based upon an XPATH query. The result nodes of the query will become the source of the data.
 * 
 * @author Walter Luetgenau
 */
public class RepositoryDataContentSource extends AbstractContentSource implements UpdatableSAXContentSource {

    String queryString;
    String nodeType = null;

    class NodeSet {
        HashSet<String> nodeIds = new HashSet<String>();

        public void add(Node node) throws RepositoryException {
            if (!node.isNodeType(Constants.JCR_REFERENCEABLE))
                return;
            nodeIds.add(node.getUUID());
        }

        public boolean contains(Node node) throws RepositoryException {
            if (!node.isNodeType(Constants.JCR_REFERENCEABLE))
                return false;
            return nodeIds.contains(node.getUUID());
        }
    }

    /** Creates a new instance of RepositoryDataContentSource */
    public RepositoryDataContentSource(Session session, String queryString, String root) {
        super(session);
        this.queryString = queryString;
        setPath(root);
    }

    public String getQueryString() {
        return queryString;
    }

    public void setQueryString(String queryString) {
        this.queryString = queryString;
    }

    /**
     * Determine the node type used in the transformation from the query
     * 
     * @return the node type
     */
    public String getNodeTypeFromQuery() {
        if (nodeType != null)
            return nodeType;

        int start = getQueryString().indexOf(Constants.QUERY_ELEMENT + "(");
        if (start < 0)
            return null;

        start = getQueryString().indexOf(",", start) + 1;
        if (start < 0)
            return null;
        int end = getQueryString().indexOf(")", start);
        if (end < 0)
            return null;

        nodeType = getQueryString().substring(start, end);
        return nodeType;
    }

    public void writeContent() throws RepositoryException, SAXException {
        if (handler == null)
            return;

        handler.startDocument();

        Workspace workspace = session.getWorkspace();
        QueryManager manager = workspace.getQueryManager();
        if (queryString.startsWith("/"))
            queryString = "/jcr:root" + queryString;
        Query query = manager.createQuery(iso9075(queryString), Query.XPATH);
        QueryResult result = query.execute();
        // Unique results
        NodeSet resultNodes = new NodeSet();

        NodeIterator it = result.getNodes();
        while (it.hasNext()) {
            Node node = it.nextNode();
            if (!resultNodes.contains(node)) {
                resultNodes.add(node);
                writeNode(node);
            }
        }

        terminatePath();

        handler.endDocument();
    }

    /**
     * Encode the path according to ISO9075
     * 
     * @param query
     * @return
     */
    protected String iso9075(String query) {
        int end = query.indexOf("//");
        if (end < 0) // not a simple query
            return query;

        String path = query.substring(0,end);
        path = ISO9075.encodePath(path); // this ties us to jackrabbit. Perhabs write an own encoder.
        return path + query.substring(end);
    }

    /**
     * Substitute the primary type of the node by the type that is part of the query.
     * This type will be used in the transformation as well.
     */
    class SecondaryQueryNodeTypeSubstituter implements NodeSubstituter {

        @Override
        public Node substitute(Node node, Node parent) throws RepositoryException {
            if (node instanceof SimpleNode || getNodeTypeFromQuery() == null)
                return node;

            for (NodeType type : node.getPrimaryNodeType().getSupertypes()) {
                String typeName = type.getName();
                if (typeName.equals(getNodeTypeFromQuery())) {
                    node = new SimpleNode(node, SimpleNode.FORMAT_USER, SimpleNode.COPYMODE_DO_NOT_COPY_CHILDREN);
                    ((SimpleNode)node).setNodeType(getNodeTypeFromQuery());
                }
            }

            return node;
        }

    }

    /**
     * Add the SecondaryQueryNodeTypeSubstituter
     * 
     * @param node
     */
   public void initSubstituters() {
        getNodeSubstituters().add(new SecondaryQueryNodeTypeSubstituter());
        super.initSubstituters();
    }


    public static void main(String[] arguments) throws Exception {
        InputStream propertiesInput = new FileInputStream("D:/Sources/nodes/web/web/test/repository.properties");
        Properties properties = new Properties();
        properties.load(propertiesInput);

        String config = properties.getProperty("config");
        String home = properties.getProperty("home");
        Repository repository = new TransientRepository(config, home);
        Session session = repository.login(
                new SimpleCredentials("username", "password".toCharArray()));

        try {
            RepositoryDataContentSource reader = new RepositoryDataContentSource(session, "/data/test/data:table2/data:columns2/data:column2", "/data/test");
            reader.setContentHandler(new PrintHandler());
            reader.writeContent();
        } finally {
            session.logout();
        }
    }
}
